// Package k8s 请求k8s API：创建部署和服务、获取Pod状态、日志，创建和获取 Chaos Mesh CRD 状态
package k8s

import (
	"bytes"
	"context"
	"fmt"
	"goproject/api/v1alpha1"
	"goproject/logger"
	"goproject/util"
	"io"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/kubernetes"
	clientv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
	clientcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
	networkingv1 "k8s.io/client-go/kubernetes/typed/networking/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"log"
	"os"
	"path/filepath"
	"time"
)

var client struct {
	*kubernetes.Clientset
	//chaosMesh ChaosMeshClient
}

func CoreV1() clientcorev1.CoreV1Interface {
	return client.CoreV1()
}

func AppsV1() clientv1.AppsV1Interface {
	return client.AppsV1()
}

func NetworkingV1() networkingv1.NetworkingV1Interface {
	return client.NetworkingV1()
}

func Init() {
	var config *rest.Config
	var err error
	// 创建集群内配置
	//config, err := rest.InClusterConfig()
	// 检查是否在集群内部运行
	if _, inCluster := os.LookupEnv("KUBERNETES_SERVICE_HOST"); inCluster {
		log.Println("在集群内部运行，使用 in-cluster 配置...")
		config, err = rest.InClusterConfig()
		if err != nil {
			log.Printf("获取 in-cluster 配置时出错: %v\n", err)
		}
	} else {
		log.Println("在本地运行，使用 kubeconfig 文件...")

		// 使用当前用户主目录下的 kubeconfig 文件路径
		kubeconfig := filepath.Join(os.Getenv("HOME"), ".kube", "config")
		log.Printf("使用 kubeconfig 文件: %s\n", kubeconfig)

		config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
		if err != nil {
			log.Printf("从 kubeconfig 构建配置时出错: %v\n", err)
		}
	}

	if err != nil {
		panic(err.Error())
	}
	// 创建客户端集
	client.Clientset, err = kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}
	// ChaosMesh客户端
	chaosMesh = newChaosMeshClient(config)
}

// 以下：微服务最初创建和最终删除

// GetPodListByLabels 通过标签获取 Pod 列表
func GetPodListByLabels(namespace string, podLabels map[string]string) ([]corev1.Pod, error) {
	podList, err := client.CoreV1().Pods(namespace).
		List(context.TODO(), metav1.ListOptions{LabelSelector: labels.FormatLabels(podLabels)})
	if err != nil {
		return nil, err
	}
	return podList.Items, nil
}

// GetPodByName 通过名字获取 Pod
func GetPodByName(namespace string, name string) (pod *corev1.Pod, err error) {
	pod, err = client.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	return
}

// 以下：检查微服务状态 和 提交操作

// 以下：执行操作后，检查和等待微服务进入特定状态

// WaitUntilInChaos 注入故障后，等待进入故障状态
func WaitUntilInChaos(name string, chaos v1alpha1.InnerObject) {
	log := logger.GetLogger()
	chaosName := chaos.GetName()
	var status ChaosStatus
	log.Infof("等待微服务 %s 进入故障 %s", name, chaosName)
	startTime := time.Now()
	err := util.RetryWithDefaultTimeoutAndInterval(func() error {
		status = GetChaosStatus(chaos)
		if status.IsInChaos() {
			// TODO 是否有注入故障后需要等待执行到位的情况?
			log.Infof("微服务 %s 进入故障 %s 历时 %v",
				name, chaosName, time.Since(startTime))
			return nil
		}
		// TODO 获取 Chaos 的状态，其中可能包括错误信息
		return fmt.Errorf("微服务 %s 尚未进入故障 %s", name, chaosName)
	})
	if err != nil {
		log.Panicf("微服务 %s 长时间未进入故障 %s: %s, 故障: %v", name, chaosName, err.Error(), chaos)
	}
}

// WaitUntilRecoverFromChaos 执行故障恢复后，等待微服务确实从故障中恢复
func WaitUntilRecoverFromChaos(aimName string, chaos v1alpha1.InnerObject) {
	log := logger.GetLogger()

	// 暂停故障
	//chaos := getChaosByResource(resource)
	chaosName := chaos.GetName()
	var status ChaosStatus
	log.Infof("等待微服务 %s 退出故障 %s", aimName, chaosName)
	startTime := time.Now()
	err := util.RetryWithDefaultTimeoutAndInterval(func() error {
		status = GetChaosStatus(chaos)
		if status.IsOutChaos() {
			// FIXME 等待确实执行了从故障中恢复?
			log.Debugf("微服务 %s 退出故障 %s 历时 %v",
				aimName, chaosName, time.Since(startTime))
			return nil
		}
		// TODO 获取 Chaos 的状态，其中可能包括错误信息
		return fmt.Errorf("微服务 %s 尚未退出故障 %s", aimName, chaosName)
	})
	if err != nil {
		log.Panicf("微服务 %s 长时间未退出故障 %s: %s, 故障: %v", aimName, chaosName, err.Error(), chaos)
	}
}

// GetKubeDNlog FIXME 与 dns.go 重复
func GetKubeDNlog(startTime time.Time) (names []string, logBytesList [][]byte) {
	log := logger.GetLogger()

	podLabels := map[string]string{
		"k8s-app": "kube-dns",
	}
	// 日志有可能比较多，所以时间限制设的长一些
	err := util.RetryWithTimeout(func() error {
		work := func() bool {
			names = nil
			logBytesList = nil

			podList, err := GetPodListByLabels(util.CoreDNSNamespace, podLabels)
			if err != nil {
				log.Warn(err.Error())
				return false
			}
			if len(podList) == 0 {
				log.Warnf("podList len %d", len(podList))
				return false
			}
			workPod := func(i int) bool {
				request := client.CoreV1().Pods(util.CoreDNSNamespace).GetLogs(podList[i].Name, &corev1.PodLogOptions{
					Timestamps: true,
					SinceTime:  &metav1.Time{Time: startTime},
				})
				podLogs, err := request.Stream(context.Background())
				if err != nil {
					log.Warn(err.Error())
					return false
				}
				defer func() {
					err := podLogs.Close()
					if err != nil {
						log.Warn(err.Error())
					}
				}()

				buf := new(bytes.Buffer)
				_, err = io.Copy(buf, podLogs)
				if err != nil {
					log.Warn(err.Error())
				}
				names = append(names, podList[i].Name)
				logBytesList = append(logBytesList, buf.Bytes())
				return true
			}
			for i := range podList {
				err := util.RetryWithDefaultTimeoutAndInterval(func() error {
					if workPod(i) {
						return nil
					}
					return fmt.Errorf("获取 k8s DNS Pod [%d] %s 日志失败", i, podList[i].Name)
				})
				if err != nil {
					log.Warn(err.Error())
					return false
				}
			}
			return true
		}
		if work() {
			log.Infof("获取 k8s DNS 日志成功")
			return nil
		}
		return fmt.Errorf("获取 k8s DNS 日志失败")
	}, 3*time.Minute, time.Second)
	if err != nil {
		log.Panic(err.Error())
	}
	return
}

func GetChaosDNSServerLog(startTime time.Time) (name string, logBytes []byte) {
	log := logger.GetLogger()

	podLabels := map[string]string{
		"app.kubernetes.io/component": "chaos-dns-server",
	}
	err := util.RetryWithDefaultTimeoutAndInterval(func() error {
		work := func() bool {
			name = ""
			logBytes = nil

			podList, err := GetPodListByLabels(util.ChaosMeshNamespace, podLabels)
			if err != nil {
				log.Warn(err.Error())
				return false
			}
			if len(podList) != 1 {
				log.Warnf("podList len %d", len(podList))
				return false
			}

			request := client.CoreV1().Pods(util.ChaosMeshNamespace).GetLogs(podList[0].Name, &corev1.PodLogOptions{
				Timestamps: true,
				SinceTime:  &metav1.Time{Time: startTime},
			})
			podLogs, err := request.Stream(context.Background())
			if err != nil {
				log.Warn(err.Error())
				return false
			}
			defer func() {
				err := podLogs.Close()
				if err != nil {
					log.Warn(err.Error())
				}
			}()

			buf := new(bytes.Buffer)
			_, err = io.Copy(buf, podLogs)
			if err != nil {
				log.Warn(err.Error())
			}
			name = podList[0].Name
			logBytes = buf.Bytes()
			return true
		}
		if work() {
			return nil
		}
		return fmt.Errorf("获取 Chaos DNS Server 日志失败")
	})
	if err != nil {
		log.Panicf("获取 Chaos DNS Server 日志失败: %s", err.Error())
	}
	return
}
